#pragma once

/**
 * @file socket_utils.h
 * @brief 套接字工具函数
 * @details 提供套接字创建、配置和管理的常用工具函数
 */

#include <iostream>
#include <string>
#include <unordered_set>
#include <sstream>
#include <sys/epoll.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
#include <ifaddrs.h>
#include <sys/socket.h>
#include <fcntl.h>

#include "macros.h"

#include "logging.h"

namespace Common {
  /**
   * @namespace Common
   * @brief 包含常用工具类和函数的命名空间
   */
  /**
   * @struct SocketCfg
   * @brief 套接字配置结构
   * @details 存储创建套接字所需的各种配置参数
   */
  struct SocketCfg {
    /**
     * @brief IP地址
     * @details 套接字绑定或连接的IP地址
     */
    std::string ip_;
    
    /**
     * @brief 网络接口名称
     * @details 如“eth0”，当IP为空时使用此接口的IP
     */
    std::string iface_;
    
    /**
     * @brief 端口号
     * @details 套接字绑定或连接的端口号，默认为-1
     */
    int port_ = -1;
    
    /**
     * @brief 是否为UDP套接字
     * @details true表示创建UDP套接字，false表示创建TCP套接字
     */
    bool is_udp_ = false;
    
    /**
     * @brief 是否为监听模式
     * @details true表示服务器模式（监听），false表示客户端模式（连接）
     */
    bool is_listening_ = false;
    
    /**
     * @brief 是否需要软件时间戳
     * @details true表示启用SO_TIMESTAMP选项，获取数据包的接收时间
     */
    bool needs_so_timestamp_ =  false;

    /**
     * @brief 将配置转换为字符串表示
     * @return 配置的字符串表示
     */
    auto toString() const {
      std::stringstream ss;
      ss << "SocketCfg[ip:" << ip_
      << " iface:" << iface_
      << " port:" << port_
      << " is_udp:" << is_udp_
      << " is_listening:" << is_listening_
      << " needs_SO_timestamp:" << needs_so_timestamp_
      << "]";

      return ss.str();
    }
  };

  /**
   * @brief TCP服务器最大未处理连接队列长度
   * @details 表示在listen()调用中指定的最大未接受TCP连接数
   */
  constexpr int MaxTCPServerBacklog = 1024;

  /**
   * @brief 将网络接口名称转换为IP地址
   * @details 如将"eth0"转换为"123.123.123.123"
   * 
   * @param iface 网络接口名称，如"eth0"
   * @return 该接口的IP地址字符串，如果失败则返回空字符串
   */
  inline auto getIfaceIP(const std::string &iface) -> std::string {
    char buf[NI_MAXHOST] = {'\0'};
    ifaddrs *ifaddr = nullptr;

    if (getifaddrs(&ifaddr) != -1) {
      for (ifaddrs *ifa = ifaddr; ifa; ifa = ifa->ifa_next) {
        if (ifa->ifa_addr && ifa->ifa_addr->sa_family == AF_INET && iface == ifa->ifa_name) {
          getnameinfo(ifa->ifa_addr, sizeof(sockaddr_in), buf, sizeof(buf), NULL, 0, NI_NUMERICHOST);
          break;
        }
      }
      freeifaddrs(ifaddr);
    }

    return buf;
  }

  /**
   * @brief 设置套接字为非阻塞模式
   * @details 套接字在读取时不会阻塞，如果没有数据可用则立即返回
   * 
   * @param fd 套接字文件描述符
   * @return 成功返回true，失败返回false
   */
  inline auto setNonBlocking(int fd) -> bool {
    const auto flags = fcntl(fd, F_GETFL, 0);
    if (flags & O_NONBLOCK)
      return true;
    return (fcntl(fd, F_SETFL, flags | O_NONBLOCK) != -1);
  }

  /**
   * @brief 禁用Nagle算法及相关延迟
   * @details 设置TCP_NODELAY选项，减少小数据包的传输延迟
   * 
   * @param fd 套接字文件描述符
   * @return 成功返回true，失败返回false
   */
  inline auto disableNagle(int fd) -> bool {
    int one = 1;
    return (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, reinterpret_cast<void *>(&one), sizeof(one)) != -1);
  }

  /**
   * @brief 允许对接收的数据包进行软件时间戳
   * @details 设置SO_TIMESTAMP选项，获取数据包的接收时间
   * 
   * @param fd 套接字文件描述符
   * @return 成功返回true，失败返回false
   */
  inline auto setSOTimestamp(int fd) -> bool {
    int one = 1;
    return (setsockopt(fd, SOL_SOCKET, SO_TIMESTAMP, reinterpret_cast<void *>(&one), sizeof(one)) != -1);
  }

  /**
   * @brief 加入指定的多播组
   * @details 将套接字添加到指定的多播流中
   * 
   * @param fd 套接字文件描述符
   * @param ip 多播组的IP地址
   * @return 成功返回true，失败返回false
   */
  inline auto join(int fd, const std::string &ip) -> bool {
    const ip_mreq mreq{{inet_addr(ip.c_str())}, {htonl(INADDR_ANY)}};
    return (setsockopt(fd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq)) != -1);
  }

  /**
   * @brief 创建TCP/UDP套接字
   * @details 根据指定的配置创建套接字，可用于连接或监听指定接口和IP:端口
   * 
   * @param logger 日志对象，用于记录套接字创建过程
   * @param socket_cfg 套接字配置参数
   * @return 创建的套接字文件描述符，失败时返回-1
   */
  [[nodiscard]] inline auto createSocket(Logger &logger, const SocketCfg& socket_cfg) -> int {
    std::string time_str;

    // 如果IP为空，则使用网络接口的IP地址
    const auto ip = socket_cfg.ip_.empty() ? getIfaceIP(socket_cfg.iface_) : socket_cfg.ip_;
    logger.log("%:% %() % cfg:%\n", __FILE__, __LINE__, __FUNCTION__,
               Common::getCurrentTimeStr(&time_str), socket_cfg.toString());

    // 设置getaddrinfo的标志，监听模式使用AI_PASSIVE
    const int input_flags = (socket_cfg.is_listening_ ? AI_PASSIVE : 0) | (AI_NUMERICHOST | AI_NUMERICSERV);
    // 初始化addrinfo结构，根据是否为UDP设置不同的协议
    const addrinfo hints{input_flags, AF_INET, socket_cfg.is_udp_ ? SOCK_DGRAM : SOCK_STREAM,
                         socket_cfg.is_udp_ ? IPPROTO_UDP : IPPROTO_TCP, 0, 0, nullptr, nullptr};

    addrinfo *result = nullptr;
    const auto rc = getaddrinfo(ip.c_str(), std::to_string(socket_cfg.port_).c_str(), &hints, &result);
    ASSERT(!rc, "getaddrinfo() failed. error:" + std::string(gai_strerror(rc)) + "errno:" + strerror(errno));

    int socket_fd = -1;
    int one = 1;
    for (addrinfo *rp = result; rp; rp = rp->ai_next) {
      ASSERT((socket_fd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol)) != -1, "socket() failed. errno:" + std::string(strerror(errno)));

      ASSERT(setNonBlocking(socket_fd), "setNonBlocking() failed. errno:" + std::string(strerror(errno)));

      if (!socket_cfg.is_udp_) { // 对TCP套接字禁用Nagle算法
        ASSERT(disableNagle(socket_fd), "disableNagle() failed. errno:" + std::string(strerror(errno)));
      }

      if (!socket_cfg.is_listening_) { // 客户端模式，连接到指定地址
        ASSERT(connect(socket_fd, rp->ai_addr, rp->ai_addrlen) != 1, "connect() failed. errno:" + std::string(strerror(errno)));
      }

      if (socket_cfg.is_listening_) { // 服务器模式，允许在bind()调用中重用地址
        ASSERT(setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, reinterpret_cast<const char *>(&one), sizeof(one)) == 0, "setsockopt() SO_REUSEADDR failed. errno:" + std::string(strerror(errno)));
      }

      if (socket_cfg.is_listening_) {
        // 绑定到指定的端口号
        const sockaddr_in addr{AF_INET, htons(socket_cfg.port_), {htonl(INADDR_ANY)}, {}};
        ASSERT(bind(socket_fd, socket_cfg.is_udp_ ? reinterpret_cast<const struct sockaddr *>(&addr) : rp->ai_addr, sizeof(addr)) == 0, "bind() failed. errno:%" + std::string(strerror(errno)));
      }

      if (!socket_cfg.is_udp_ && socket_cfg.is_listening_) { // TCP服务器模式，监听传入的连接
        ASSERT(listen(socket_fd, MaxTCPServerBacklog) == 0, "listen() failed. errno:" + std::string(strerror(errno)));
      }

      if (socket_cfg.needs_so_timestamp_) { // 启用软件接收时间戳
        ASSERT(setSOTimestamp(socket_fd), "setSOTimestamp() failed. errno:" + std::string(strerror(errno)));
      }
    }

    return socket_fd;
  }
}
